<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgpu - SSGI</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="example.css">
	</head>
	<body>

		<div id="info">
			<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

			<div class="title-wrapper">
				<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>SSGI</span>
			</div>

			<small>Real-time indirect illumination and ambient occlusion using screen-space information.</small>
		</div>

		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.webgpu.js",
					"three/webgpu": "../build/three.webgpu.js",
					"three/tsl": "../build/three.tsl.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

		<script type="module">

			import * as THREE from 'three/webgpu';
			import { pass, mrt, output, normalView, diffuseColor, velocity, add, vec3, vec4, directionToColor, colorToDirection, sample } from 'three/tsl';
			import { ssgi } from 'three/addons/tsl/display/SSGINode.js';
			import { traa } from 'three/addons/tsl/display/TRAANode.js';

			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

			import { Inspector } from 'three/addons/inspector/Inspector.js';

			let camera, scene, renderer, postProcessing, controls;

			init();

			async function init() {

				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 100 );
				camera.position.set( 0, 10, 30 );

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xaaaaaa );

				renderer = new THREE.WebGPURenderer();
				//renderer.setPixelRatio( window.devicePixelRatio ); // probably too costly for most hardware
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.setAnimationLoop( animate );
				renderer.shadowMap.enabled = true;
				renderer.inspector = new Inspector();
				document.body.appendChild( renderer.domElement );

				//

				controls = new OrbitControls( camera, renderer.domElement );
				controls.target.set( 0, 7, 0 );
				controls.enablePan = true;
				controls.minDistance = 1;
				controls.maxDistance = 100;
				controls.update();

				//

				postProcessing = new THREE.PostProcessing( renderer );

				const scenePass = pass( scene, camera );
				scenePass.setMRT( mrt( {
					output: output,
					diffuseColor: diffuseColor,
					normal: directionToColor( normalView ),
					velocity: velocity
				} ) );

				const scenePassColor = scenePass.getTextureNode( 'output' );
				const scenePassDiffuse = scenePass.getTextureNode( 'diffuseColor' ).toInspector( 'Diffuse Color' );
				const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {

					return scenePass.getLinearDepthNode();

				} );

				const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal' );
				const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );

				// bandwidth optimization

				const diffuseTexture = scenePass.getTexture( 'diffuseColor' );
				diffuseTexture.type = THREE.UnsignedByteType;

				const normalTexture = scenePass.getTexture( 'normal' );
				normalTexture.type = THREE.UnsignedByteType;

				const sceneNormal = sample( ( uv ) => {

					return colorToDirection( scenePassNormal.sample( uv ) );

				} );

				// gi

				const giPass = ssgi( scenePassColor, scenePassDepth, sceneNormal, camera );
				giPass.sliceCount.value = 2;
				giPass.stepCount.value = 8;

				// composite

				const gi = giPass.rgb.toInspector( 'SSGI' );
				const ao = giPass.a.toInspector( 'AO' );

				const compositePass = vec4( add( scenePassColor.rgb.mul( ao ), ( scenePassDiffuse.rgb.mul( gi ) ) ), scenePassColor.a );
				compositePass.name = 'Composite';

				// traa

				const traaPass = traa( compositePass, scenePassDepth, scenePassVelocity, camera );
				postProcessing.outputNode = traaPass;

				// Cornell Box inspired scene

				// Walls
				const wallGeometry = new THREE.PlaneGeometry( 1, 1 );

				// Left wall - red
				const redWallMaterial = new THREE.MeshPhysicalMaterial( { color: '#ff0000' } );
				const leftWall = new THREE.Mesh( wallGeometry, redWallMaterial );
				leftWall.scale.set( 20, 15, 1 );
				leftWall.rotation.y = Math.PI * 0.5;
				leftWall.position.set( - 10, 7.5, 0 );
				leftWall.receiveShadow = true;
				scene.add( leftWall );

				// Right wall - green
				const greenWallMaterial = new THREE.MeshPhysicalMaterial( { color: '#00ff00' } );
				const rightWall = new THREE.Mesh( wallGeometry, greenWallMaterial );
				rightWall.scale.set( 20, 15, 1 );
				rightWall.rotation.y = Math.PI * - 0.5;
				rightWall.position.set( 10, 7.5, 0 );
				rightWall.receiveShadow = true;
				scene.add( rightWall );

				// White walls and boxes
				const whiteMaterial = new THREE.MeshPhysicalMaterial( { color: '#fff' } );

				// Floor
				const floor = new THREE.Mesh( wallGeometry, whiteMaterial );
				floor.scale.set( 20, 20, 1 );
				floor.rotation.x = Math.PI * - .5;
				floor.receiveShadow = true;
				scene.add( floor );

				// Back wall
				const backWall = new THREE.Mesh( wallGeometry, whiteMaterial );
				backWall.scale.set( 15, 20, 1 );
				backWall.rotation.z = Math.PI * - 0.5;
				backWall.position.set( 0, 7.5, - 10 );
				backWall.receiveShadow = true;
				scene.add( backWall );

				// Ceiling
				const ceiling = new THREE.Mesh( wallGeometry, whiteMaterial );
				ceiling.scale.set( 20, 20, 1 );
				ceiling.rotation.x = Math.PI * 0.5;
				ceiling.position.set( 0, 15, 0 );
				ceiling.receiveShadow = true;
				scene.add( ceiling );

				// Boxes
				const tallBoxGeometry = new THREE.BoxGeometry( 5, 7, 5 );
				const tallBox = new THREE.Mesh( tallBoxGeometry, whiteMaterial );
				tallBox.rotation.y = Math.PI * 0.25;
				tallBox.position.set( - 3, 3.5, - 2 );
				tallBox.castShadow = true;
				tallBox.receiveShadow = true;
				scene.add( tallBox );

				const shortBoxGeometry = new THREE.BoxGeometry( 4, 4, 4 );
				const shortBox = new THREE.Mesh( shortBoxGeometry, whiteMaterial );
				shortBox.rotation.y = Math.PI * - 0.1;
				shortBox.position.set( 4, 2, 4 );
				shortBox.castShadow = true;
				shortBox.receiveShadow = true;
				scene.add( shortBox );

				// Light source geometry
				const lightSourceGeometry = new THREE.CylinderGeometry( 2.5, 2.5, 1, 64 );
				const lightSourceMaterial = new THREE.MeshBasicMaterial();
				const lightSource = new THREE.Mesh( lightSourceGeometry, lightSourceMaterial );
				lightSource.position.y = 15;
				scene.add( lightSource );

				// Point light
				const pointLight = new THREE.PointLight( '#ffffff', 100 );
				pointLight.position.set( 0, 13, 0 );
				pointLight.distance = 100;
				pointLight.castShadow = true;
				pointLight.shadow.mapSize.width = 1024;
				pointLight.shadow.mapSize.height = 1024;
				pointLight.shadow.bias = - 0.0025;
				scene.add( pointLight );

				// Ambient light
				const ambientLight = new THREE.AmbientLight( '#0c0c0c' );
				scene.add( ambientLight );

				window.addEventListener( 'resize', onWindowResize );

				//

				const params = {
					output: 0
				};

				const types = { Combined: 0, Direct: 3, AO: 1, GI: 2 };

				const gui = renderer.inspector.createParameters( 'SSGI settings' );
				gui.add( params, 'output', types ).onChange( updatePostprocessing );
				gui.add( giPass.sliceCount, 'value', 1, 4, 1 ).name( 'slice count' );
				gui.add( giPass.stepCount, 'value', 1, 32, 1 ).name( 'step count' );
				gui.add( giPass.radius, 'value', 1, 25 ).name( 'radius' );
				gui.add( giPass.expFactor, 'value', 1, 3 ).name( 'exp factor' );
				gui.add( giPass.thickness, 'value', 0.01, 10 ).name( 'thickness' );
				gui.add( giPass.backfaceLighting, 'value', 0, 1 ).name( 'backface lighting' );
				gui.add( giPass.aoIntensity, 'value', 0, 4 ).name( 'AO intensity' );
				gui.add( giPass.giIntensity, 'value', 0, 100 ).name( 'GI intensity' );
				gui.add( giPass.useLinearThickness, 'value' ).name( 'use linear thickness' );
				gui.add( giPass.useScreenSpaceSampling, 'value' ).name( 'screen-space sampling' );
				gui.add( giPass, 'useTemporalFiltering' ).name( 'temporal filtering' ).onChange( updatePostprocessing );

				function updatePostprocessing( value ) {

					if ( value === 1 ) {

						postProcessing.outputNode = vec4( vec3( ao ), 1 );

					} else if ( value === 2 ) {

						postProcessing.outputNode = vec4( gi, 1 );

					} else if ( value === 3 ) {

						postProcessing.outputNode = scenePassColor;

					} else {

						postProcessing.outputNode = giPass.useTemporalFiltering ? traaPass : compositePass;

					}

					postProcessing.needsUpdate = true;


				}


			}

			function onWindowResize() {

				const width = window.innerWidth;
				const height = window.innerHeight;

				camera.aspect = width / height;
				camera.updateProjectionMatrix();

				renderer.setSize( width, height );

			}

			function animate() {

				controls.update();

				postProcessing.render();

			}

		</script>
	</body>
</html>
